/*
 * Copyright 2019 The ZuSmart Project
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zusmart.base.logging.format;

import java.util.HashSet;
import java.util.Set;

/**
 * @author Administrator
 *
 */
public final class Formatter {

	private static final String PLACEHODLER = "{}";
	private static final char ESCAPE_CHAR = '\\';

	public static FormatterTuple format(final String pattern, final Object arg) {
		return format(pattern, new Object[] { arg });
	}

	public static FormatterTuple format(final String pattern, final Object argA, final Object argB) {
		return format(pattern, new Object[] { argA, argB });
	}

	public static FormatterTuple format(final String pattern, final Object[] args) {
		if (null == args || args.length == 0) {
			return new FormatterTuple(pattern, null);
		}
		int idx = args.length - 1;
		Object entry = args[idx];
		Throwable throwable = entry instanceof Throwable ? (Throwable) entry : null;
		if (null == pattern) {
			return new FormatterTuple(null, throwable);
		}
		int j = pattern.indexOf(PLACEHODLER);
		if (j == -1) {
			return new FormatterTuple(pattern, throwable);
		}
		StringBuilder sb = new StringBuilder(pattern.length() + 50);
		int i = 0;
		int L = 0;
		do {
			boolean notEscaped = j == 0 || pattern.charAt(j - 1) != ESCAPE_CHAR;
			if (notEscaped) {
				sb.append(pattern, i, j);
			} else {
				sb.append(pattern, i, j - 1);
				notEscaped = j >= 2 && pattern.charAt(j - 2) == ESCAPE_CHAR;
			}
			i = j + 2;
			if (notEscaped) {
				deeplyAppendParameter(sb, args[L], null);
				L++;
				if (L > idx) {
					break;
				}
			} else {
				sb.append(PLACEHODLER);
			}
			j = pattern.indexOf(PLACEHODLER, i);
		} while (j != -1);
		sb.append(pattern, i, pattern.length());
		return new FormatterTuple(sb.toString(), L <= idx ? throwable : null);
	}

	private static void deeplyAppendParameter(StringBuilder sb, Object obj, Set<Object[]> set) {
		if (obj == null) {
			sb.append("null");
			return;
		}
		Class<?> objClass = obj.getClass();
		if (!objClass.isArray()) {
			if (Number.class.isAssignableFrom(objClass)) {
				if (objClass == Long.class) {
					sb.append(((Long) obj).longValue());
				} else if (objClass == Integer.class || objClass == Short.class || objClass == Byte.class) {
					sb.append(((Number) obj).intValue());
				} else if (objClass == Double.class) {
					sb.append(((Double) obj).doubleValue());
				} else if (objClass == Float.class) {
					sb.append(((Float) obj).floatValue());
				} else {
					safeObjectAppend(sb, obj);
				}
			} else {
				safeObjectAppend(sb, obj);
			}
		} else {
			sb.append('[');
			if (objClass == boolean[].class) {
				booleanArrayAppend(sb, (boolean[]) obj);
			} else if (objClass == byte[].class) {
				byteArrayAppend(sb, (byte[]) obj);
			} else if (objClass == char[].class) {
				charArrayAppend(sb, (char[]) obj);
			} else if (objClass == short[].class) {
				shortArrayAppend(sb, (short[]) obj);
			} else if (objClass == int[].class) {
				intArrayAppend(sb, (int[]) obj);
			} else if (objClass == long[].class) {
				longArrayAppend(sb, (long[]) obj);
			} else if (objClass == float[].class) {
				floatArrayAppend(sb, (float[]) obj);
			} else if (objClass == double[].class) {
				doubleArrayAppend(sb, (double[]) obj);
			} else {
				objectArrayAppend(sb, (Object[]) obj, set);
			}
			sb.append(']');
		}
	}

	private static void shortArrayAppend(StringBuilder sb, short[] arg) {
		if (arg.length == 0) {
			return;
		}
		sb.append(arg[0]);
		for (int i = 1; i < arg.length; i++) {
			sb.append(", ");
			sb.append(arg[i]);
		}
	}

	private static void intArrayAppend(StringBuilder sb, int[] arg) {
		if (arg.length == 0) {
			return;
		}
		sb.append(arg[0]);
		for (int i = 1; i < arg.length; i++) {
			sb.append(", ");
			sb.append(arg[i]);
		}
	}

	private static void longArrayAppend(StringBuilder sb, long[] arg) {
		if (arg.length == 0) {
			return;
		}
		sb.append(arg[0]);
		for (int i = 1; i < arg.length; i++) {
			sb.append(", ");
			sb.append(arg[i]);
		}
	}

	private static void floatArrayAppend(StringBuilder sb, float[] arg) {
		if (arg.length == 0) {
			return;
		}
		sb.append(arg[0]);
		for (int i = 1; i < arg.length; i++) {
			sb.append(", ");
			sb.append(arg[i]);
		}
	}

	private static void doubleArrayAppend(StringBuilder sb, double[] arg) {
		if (arg.length == 0) {
			return;
		}
		sb.append(arg[0]);
		for (int i = 1; i < arg.length; i++) {
			sb.append(", ");
			sb.append(arg[i]);
		}
	}

	private static void charArrayAppend(StringBuilder sb, char[] arg) {
		if (arg.length == 0) {
			return;
		}
		sb.append(arg[0]);
		for (int i = 1; i < arg.length; i++) {
			sb.append(", ");
			sb.append(arg[i]);
		}
	}

	private static void byteArrayAppend(StringBuilder sb, byte[] arg) {
		if (arg.length == 0) {
			return;
		}
		sb.append(arg[0]);
		for (int i = 1; i < arg.length; i++) {
			sb.append(", ");
			sb.append(arg[i]);
		}
	}

	private static void booleanArrayAppend(StringBuilder sb, boolean[] arg) {
		if (arg.length == 0) {
			return;
		}
		sb.append(arg[0]);
		for (int i = 1; i < arg.length; i++) {
			sb.append(", ");
			sb.append(arg[i]);
		}
	}

	private static void objectArrayAppend(StringBuilder sb, Object[] arg, Set<Object[]> set) {
		if (arg.length == 0) {
			return;
		}
		if (set == null) {
			set = new HashSet<Object[]>(arg.length);
		}
		if (set.add(arg)) {
			deeplyAppendParameter(sb, arg[0], set);
			for (int i = 1; i < arg.length; i++) {
				sb.append(", ");
				deeplyAppendParameter(sb, arg[i], set);
			}
			set.remove(arg);
		} else {
			sb.append("...");
		}
	}

	private static void safeObjectAppend(StringBuilder sb, Object obj) {
		try {
			String oAsString = obj.toString();
			sb.append(oAsString);
		} catch (Throwable t) {
			sb.append("[FAILED toString()]");
		}
	}

}